package birdsong.comm;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.LinkedList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import birdsong.comm.ClientSideMessage.Type;

/**
 * Communication manager for {@link birdsong.client.BirdsongServer Birdsong
 * server}. Birdsong server have this object to manage all the messages sent by
 * the clients.
 * 
 * @author While True
 * @version 1
 */

public class DealWithClient {

	private BlockingQueue<ClientMessage> serverSenderMessageList = new LinkedBlockingQueue<ClientMessage>();
	private LinkedList<Thread> listThreadsActive = new LinkedList<Thread>(); 
	private ObjectOutputStream out;
	private ObjectInputStream in;
	private final Socket socket;
	private String clientNickname;
	private DealWithClient dealer = this;

	private final ServerCommunicator serverCommunicator;

	/**
	 * Constructor that receives a socket and a ServerCommunicator. Will
	 * generate threads to take care of {@link OutputStream} and
	 * {@link InputStream}. Will add all threads generated to listThreadsActive
	 * for interruption purposes.
	 * 
	 * @param socket
	 *            Socket for the network interface.
	 * @param clientCommunicator
	 *            ClientCommunicator responsible for the communication of the
	 *            client.
	 * @throws IOException
	 *             Send an exception if the socket is closed.
	 */
	public DealWithClient(Socket socket, ServerCommunicator serverCommunicator) throws IOException {
		this.socket = socket;
		this.serverCommunicator = serverCommunicator;
		out = new ObjectOutputStream(socket.getOutputStream());
		in = new ObjectInputStream(socket.getInputStream());
		getListThreadsActive().add(new ThreadSenderClient());
		getListThreadsActive().add(new ThreadReceiverClient());
		
	}

	private class ThreadReceiverClient extends Thread {  
		@Override
		public void run() {
			try { 
				while (!isInterrupted()) { 
					ServerMessage message = (ServerMessage) getIn().readObject();
					synchronized (serverCommunicator) {
						if (message.getType().equals(ServerSideMessage.Type.CLIENT_CONNECTED)) {
							if (getServerCommunicator().getMapDealWithClients().get(message.getClientNickname()) == null) {
								setClientNickname(message.getClientNickname());
								getServerCommunicator().getMapDealWithClients().put(getClientNickname(), getDealer());
								serverCommunicator.getServerMessageListReceived().add(message);
							} else {
								out.writeObject(new ClientMessage(Type.ERROR, "ERROR: client already exists", clientNickname));
								out.close();
								in.close();
							}
						} else 
							serverCommunicator.getServerMessageListReceived().add(message); 
					}
				}
			} catch (Exception e) {}
		}
	}
 
	private class ThreadSenderClient extends Thread { 
		@Override
		public void run() { 
			try {
				while (!isInterrupted()) { 
					ClientMessage message = getServerSenderMessageList().take();
					getOut().writeObject(message);
				}
			} catch (Exception e) {}
			
		}
	}

	/**
	 * Starts all created threads.
	 */
	public void start() {
		
		for (Thread listThreadsActive : getListThreadsActive()) {
			System.out.println("Starting threads...");
			listThreadsActive.start();
		} 
	}

	/**
	 * Getter that returns the socket.
	 * 
	 * @return Returns the socket.
	 */
	public Socket getSocket() {
		return socket;
	}

	/**
	 * Getter that returns the ServerCommunicator.
	 * 
	 * @return Returns the ServerCommunicatort.
	 */
	public ServerCommunicator getServerCommunicator() {
		return serverCommunicator;
	}

	/**
	 * Getter that returns the OutputStreamer.
	 * 
	 * @return Returns the ObjectInputStream.
	 */
	public ObjectOutputStream getOut() {
		return out;
	}

	/**
	 * Getter that returns the InputStreamer.
	 * 
	 * @return Returns the ObjectInputStream.
	 */
	public ObjectInputStream getIn() {
		return in;
	}

	/**
	 * Setter that sets the client nickname.
	 */
	public void setClientNickname(String clientNickname) {
		this.clientNickname = clientNickname;
	}

	/**
	 * Getter that returns the client nickname.
	 * 
	 * @return Returns the clientNickname.
	 */
	public String getClientNickname() {
		return clientNickname;
	}

	/**
	 * Getter that returns the list of messages to be sent to the client.
	 * 
	 * @return Returns the serverSenderMessageList.
	 */
	public BlockingQueue<ClientMessage> getServerSenderMessageList() {
		return serverSenderMessageList;
	}

	/**
	 * Getter that returns the list of active threads.
	 * 
	 * @return Returns the listThreadsActive.
	 */
	public LinkedList<Thread> getListThreadsActive() {
		return listThreadsActive;
	}

	/**
	 * Getter that returns itself.
	 * 
	 * @return Returns the instance of the class.
	 */
	public DealWithClient getDealer() {
		return dealer;
	}

}